package com.cloudfun.campusshare.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.AsyncTaskDTO;
import com.cloudfun.campusshare.common.model.vo.RollerWasherOpVO;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.common.security.UserAuthentication;
import com.cloudfun.campusshare.entity.DeviceEntity;
import com.cloudfun.campusshare.service.AsyncTaskService;
import com.cloudfun.campusshare.service.DeviceOpServiceDelegate;
import com.cloudfun.campusshare.service.DeviceService;
import com.cloudfun.campusshare.service.DeviceTestService;
import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * Created by Huqin on 2020/6/4
 */
@Slf4j
@Service
// TODO redis分布式锁
public class DeviceTestServiceImpl implements DeviceTestService {

    @Autowired
    private DeviceOpServiceDelegate deviceOpService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private AsyncTaskService asyncTaskService;

    @Autowired
    private DeviceService deviceService;

    @Override
    public void startTest(AsyncTaskBizType bizType, String imei) {
        log.info("添加可靠性测试任务：{}", imei);
        DeviceEntity deviceEntity = deviceService.getDeviceWithAuthByImei(imei);
        if (!StringUtils.equals(deviceEntity.getDeviceType(), BaseConstant.GXY8_NAME)
                && !StringUtils.equals(deviceEntity.getDeviceType(), BaseConstant.GXY13_5_NAME)) {
            throw new BusinessException(Code.ERROR, "设备类型错误");
        }
        if (!BooleanUtils.isTrue(deviceEntity.getOnline())) {
            throw new BusinessException(Code.ERROR, "设备离线");
        }
        AsyncTaskDTO asyncTaskDTO = asyncTaskService.getLast(bizType, deviceEntity.getId());
        // 没有任务 创建新任务
        if (null == asyncTaskDTO) {
            // 添加任务
            asyncTaskDTO = asyncTaskService.create(bizType, deviceEntity.getId(), 10_000, deviceEntity.getImei());
            this.submitTask(asyncTaskDTO);
            return;
        }
        // 有任务 切任务还在进行中
        if (asyncTaskDTO.getStatus().equals(AsyncTaskStatus.NEW) || asyncTaskDTO.getStatus().equals(AsyncTaskStatus.RUNNING)) {
            throw new BusinessException(Code.ERROR, "任务进行中");
        }
        // 如果任务已经取消或完成 创建新任务
        if (asyncTaskDTO.getStatus().equals(AsyncTaskStatus.CANCELED) || asyncTaskDTO.getStatus().equals(AsyncTaskStatus.TERMINAL)) {
            // 添加任务
            asyncTaskDTO = asyncTaskService.create(bizType, deviceEntity.getId(), 10_000, deviceEntity.getImei());
            this.submitTask(asyncTaskDTO);
            return;
        }
        // 异常中断任务，继续执行
        if (asyncTaskDTO.getStatus().equals(AsyncTaskStatus.ERROR_CANCEL)) {
            asyncTaskService.updateStatus(asyncTaskDTO.getId(), AsyncTaskStatus.RUNNING);
            // 添加任务
            this.submitTask(asyncTaskDTO);
        }
    }

    @Override
    public Integer testLoopCount(AsyncTaskBizType bizType, String imei) {
        DeviceEntity deviceEntity = deviceService.getDeviceWithoutAuthByImei(imei);
        AsyncTaskDTO asyncTaskDTO = asyncTaskService.getLast(bizType, deviceEntity.getId());
        if (null == asyncTaskDTO) {
            return null;
        }
        return asyncTaskDTO.getActualCount();
    }

    @Override
    public void cancelTest(AsyncTaskBizType bizType, String imei) {
        DeviceEntity deviceEntity = deviceService.getDeviceWithoutAuthByImei(imei);
        AsyncTaskDTO asyncTaskDTO = asyncTaskService.getLast(bizType, deviceEntity.getId());
        if (null == asyncTaskDTO) {
            return;
        }
        asyncTaskDTO.setStatus(AsyncTaskStatus.CANCELED);
        asyncTaskService.modify(asyncTaskDTO);
        if (DeviceStatus.RUNNING == deviceEntity.getStatus()) {
            // 运行中则复位
            deviceOpService.reset(imei);
        }
    }

    //@PostConstruct
    public void loadDeviceTestTask() {
        // 重启时加载
        List<AsyncTaskDTO> runnableTask = asyncTaskService.getRunnable(AsyncTaskBizType.DEVICE_RELIABILITY);
        runnableTask.addAll(asyncTaskService.getRunnable(AsyncTaskBizType.DEVICE_INFINITY_LOOP));
        for (AsyncTaskDTO asyncTaskDTO : runnableTask) {
            this.submitTask(asyncTaskDTO);
        }
        log.error("加载{}个设备测试任务", runnableTask.size());
    }

    /**
     * 提交给线程池执行
     *
     * @param asyncTaskDTO
     */
    private void submitTask(AsyncTaskDTO asyncTaskDTO) {
        BaseConstant.SCHEDULED_EXECUTOR.execute(() -> {
            this.fillAdminAuthHolder();
            try {
                this.doExecuteTest(asyncTaskDTO);
            } catch (Exception e) {
                log.error("设备测试任务执行时异常：" + e);
            } finally {
                AuthenticationHolder.clear();
            }
        });
    }

    /**
     * 执行测试
     *
     * @param asyncTaskDTO
     */
    private void doExecuteTest(AsyncTaskDTO asyncTaskDTO) {
        if (asyncTaskDTO.getActualCount() >= asyncTaskDTO.getExpectedCount()) {
            return;
        }
        asyncTaskDTO.setStatus(AsyncTaskStatus.RUNNING);
        asyncTaskService.modify(asyncTaskDTO);

        String imei = JSONObject.parseObject(asyncTaskDTO.getParams(), String.class);
        DeviceEntity deviceEntity = deviceService.getDeviceWithoutAuthByImei(imei);
        List<DelayTask> tasks = new ArrayList<>();
        if (DeviceStatus.RUNNING == deviceEntity.getStatus()) {
            // 运行中则复位
            deviceOpService.reset(imei);
            //this.deviceStatusCheckTask(DeviceStatus.NOT_RUNNING, imei, 5L, asyncTaskDTO.getId(), tasks);
        }
        // 程序参数
        RollerWasherOpVO rollerWasherOpVO = new RollerWasherOpVO();
        rollerWasherOpVO.setImei(imei);
        rollerWasherOpVO.setDeviceType(deviceEntity.getDeviceType());
        rollerWasherOpVO.setOrderId("placeholder");

        // 测试
        switch (asyncTaskDTO.getBizType()) {
            case DEVICE_RELIABILITY:
                this.doReliableTest(asyncTaskDTO, rollerWasherOpVO, tasks);
                break;
            case DEVICE_INFINITY_LOOP:
                this.doInfinityLoopTest(asyncTaskDTO, rollerWasherOpVO, tasks);
                break;
            default:
                log.error("设备测试任务类型错误：{}", asyncTaskDTO.getBizType());
                asyncTaskDTO.setStatus(AsyncTaskStatus.ERROR_CANCEL);
                asyncTaskService.modify(asyncTaskDTO);
                return;
        }

        // 设置已完成的循环数
        tasks.add(new DelayTask(1L, asyncTaskDTO.getId(), "设置已完成的循环数", () -> {
            log.debug("设备【{}】的可靠性测试任务完成{}次", deviceEntity.getId(), asyncTaskDTO.getActualCount());
            // 更新
            asyncTaskService.modify(asyncTaskDTO);
            return true;
        }));

        // 计划下一批任务
        tasks.add(new DelayTask(5L, asyncTaskDTO.getId(), "计划下一批任务", () -> {
            // 重新查询任务，看是否被取消
            AsyncTaskDTO taskDTO = asyncTaskService.get(asyncTaskDTO.getId());
            if (null == taskDTO || taskDTO.getStatus().equals(AsyncTaskStatus.CANCELED) || taskDTO.getStatus().equals(AsyncTaskStatus.ERROR_CANCEL)) {
                log.debug("可靠性测试任务: 【{}】取消", deviceEntity.getId());
            } else {
                this.doExecuteTest(asyncTaskDTO);
            }
            return true;
        }));
        // 任务入池
        DelayTask head = null;
        DelayTask pre = null;
        for (DelayTask curr : tasks) {
            if (null == head) {
                head = curr;
            }
            if (null != pre) {
                pre.next = curr;
            }
            pre = curr;
        }
        this.schedule(head);
    }

    private void doReliableTest(AsyncTaskDTO asyncTaskDTO, RollerWasherOpVO rollerWasherOpVO, List<DelayTask> tasks) {
        String imei = rollerWasherOpVO.getImei();
        int count = asyncTaskDTO.getActualCount();

        // 验证码
        tasks.add(new DelayTask(5L, asyncTaskDTO.getId(), "验证码", () -> {
            deviceOpService.deviceVerifyCode(imei, StringUtils.leftPad(String.valueOf(count), 4, '0'));
            return true;
        }));
        // 标准
        tasks.add(new DelayTask(2L, asyncTaskDTO.getId(), "标准程序", () -> {
            rollerWasherOpVO.setWashType("1");
            deviceOpService.wash(rollerWasherOpVO);
            return true;
        }));
        tasks.add(new DelayTask(2L, asyncTaskDTO.getId(), "复位", () -> {
            deviceOpService.reset(imei);
            return true;
        }));
        // 快速
        tasks.add(new DelayTask(5L, asyncTaskDTO.getId(), "快速程序", () -> {
            rollerWasherOpVO.setWashType("2");
            deviceOpService.wash(rollerWasherOpVO);
            return true;
        }));
        tasks.add(new DelayTask(1L, asyncTaskDTO.getId(), "复位", () -> {
            deviceOpService.reset(imei);
            return true;
        }));
        // 大物
        tasks.add(new DelayTask(5L, asyncTaskDTO.getId(), "大物程序", () -> {
            rollerWasherOpVO.setWashType("3");
            deviceOpService.wash(rollerWasherOpVO);
            return true;
        }));
        tasks.add(new DelayTask(1L, asyncTaskDTO.getId(), "复位", () -> {
            deviceOpService.reset(imei);
            return true;
        }));
        // 单脱
        tasks.add(new DelayTask(5L, asyncTaskDTO.getId(), "单脱程序", () -> {
            rollerWasherOpVO.setWashType("4");
            deviceOpService.wash(rollerWasherOpVO);
            return true;
        }));
        tasks.add(new DelayTask(1L, asyncTaskDTO.getId(), "复位&设置运行次数", () -> {
            deviceOpService.reset(imei);
            asyncTaskDTO.setActualCount(count + 1);
            return true;
        }));
    }

    private void doInfinityLoopTest(AsyncTaskDTO asyncTaskDTO, RollerWasherOpVO rollerWasherOpVO, List<DelayTask> tasks) {
        String imei = rollerWasherOpVO.getImei();
        int count = asyncTaskDTO.getActualCount();
        // 验证码
        tasks.add(new DelayTask(5L, asyncTaskDTO.getId(), "验证码", () -> {
            deviceOpService.deviceVerifyCode(imei, StringUtils.leftPad(String.valueOf(count), 4, '0'));
            return true;
        }));
        // 标准
        rollerWasherOpVO.setWashType("1");
        tasks.add(new DelayTask(2L, asyncTaskDTO.getId(), "标准程序", () -> {
            deviceOpService.wash(rollerWasherOpVO);
            return true;
        }));

        // 前置任务，只有前置任务返回true，才能执行任务。
        DelayTask pre = new DelayTask(30L, asyncTaskDTO.getId(), "等待程序运行结束前置任务", () -> {
            DeviceEntity deviceEntity = deviceService.getDeviceWithoutAuthByImei(rollerWasherOpVO.getImei());
            return deviceEntity.getStatus() == DeviceStatus.NOT_RUNNING;
        });
        DelayTask delayTask = new DelayTask(1L, asyncTaskDTO.getId(), "设置运行次数", pre, () -> {
            asyncTaskDTO.setActualCount(count + 1);
            return true;
        });
        tasks.add(delayTask);
    }

    public void schedule(DelayTask delayTask) {
        log.info("提交任务：【{}】", delayTask.getName());
        BaseConstant.SCHEDULED_EXECUTOR.schedule(() -> {
            AsyncTaskDTO taskDTO = asyncTaskService.get(delayTask.getTaskId());
            if (null == taskDTO || taskDTO.getStatus().equals(AsyncTaskStatus.CANCELED) || taskDTO.getStatus().equals(AsyncTaskStatus.ERROR_CANCEL)) {
                log.info("设备测试任务: 【{}】取消", delayTask.getTaskId());
                return;
            }

            // 调度前置任务
            DelayTask prerequisite = delayTask.getPrerequisite();
            if (null != prerequisite) {
                delayTask.setPrerequisite(null);
                this.schedule(prerequisite);
                return;
            }
            boolean isPrerequisiteTask = delayTask.isPrerequisiteTask();
            // 前置任务不需要重试
            Boolean res = this.doExecuteTask(delayTask, !isPrerequisiteTask);
            log.info("设备测试任务：【{}】执行结果：{}", delayTask.getName(), res);
            if (BooleanUtils.isTrue(res)) {
                if (null != delayTask.next) {
                    // 计划下一个任务
                    this.schedule(delayTask.next);
                }
            } else {
                // 任务返回失败，如果是前置任务，则调度前置任务；否则，测试任务失败
                if (isPrerequisiteTask) {
                    this.schedule(delayTask);
                } else {
                    asyncTaskService.updateStatus(delayTask.getTaskId(), AsyncTaskStatus.ERROR_CANCEL);
                    log.error("设备测试任务: 【{}】中断", delayTask.getTaskId());
                }
            }
        }, delayTask.getDelay(), TimeUnit.SECONDS);
    }

    @SneakyThrows
    private Boolean doExecuteTask(DelayTask delayTask, boolean retry) {
        this.fillAdminAuthHolder();
        boolean res = false;
        try {
            for (int i = 0; i < 20; i++) {
                try {
                    res = delayTask.getTask().call();
                    break;
                } catch (Exception e) {
                    log.info("设备测试任务：【{}】执行失败: {}", delayTask.getName(), e);
                    if (!retry) {
                        break;
                    }
                    Thread.sleep(500);
                }
            }
        } finally {
            AuthenticationHolder.clear();
        }
        return res;
    }

    private void fillAdminAuthHolder() {
        UserAuthentication userAuth = new UserAuthentication("-1", "admin", Sets.newHashSet(Arrays.asList(RoleType.ADMIN.name())));
        AuthenticationHolder.setUserAuth(userAuth);
    }

    @Getter
    @Setter
    static class DelayTask {
        protected final long delay;
        protected final String taskId;
        private final Callable<Boolean> task;
        private DelayTask prerequisite;
        private DelayTask next;
        private String name;
        private boolean prerequisiteTask;

        public DelayTask(long delay, String taskId, String name, Callable<Boolean> task) {
            this.delay = delay;
            this.taskId = taskId;
            this.name = name;
            this.task = task;
        }

        public DelayTask(long delay, String taskId, String name, DelayTask prerequisite, Callable<Boolean> task) {
            this.delay = delay;
            this.taskId = taskId;
            this.name = taskId + "-" + name;
            prerequisite.setPrerequisiteTask(true);
            this.prerequisite = prerequisite;
            this.prerequisite.next = this;
            this.task = task;
        }
    }

}
